/*!
 * jQuery Color Animations v@VERSION
 * https://github.com/jquery/jquery-color
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * Date: @DATE
 */

(function(root, factory) {
  if (typeof define === "function" && define.amd) {

    // AMD. Register as an anonymous module.
    define(["jquery"], factory);
  } else if (typeof exports === "object") {
    module.exports = factory(require("jquery"));
  } else {
    factory(root.jQuery);
  }
})(this, function(jQuery, undefined) {

  var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor " +
    "borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",

    // plusequals test for += 100 -= 100
    rplusequals = /^([\-+])=\s*(\d+\.?\d*)/,

    // a set of RE's that can match strings and generate color tuples.
    stringParsers = [{
      re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,
      parse: function(execResult) {
        return [
          execResult[1],
          execResult[2],
          execResult[3],
          execResult[4]
        ];
      }
    }, {
      re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,
      parse: function(execResult) {
        return [
          execResult[1] * 2.55,
          execResult[2] * 2.55,
          execResult[3] * 2.55,
          execResult[4]
        ];
      }
    }, {

      // this regex ignores A-F because it's compared against an already lowercased string
      re: /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,
      parse: function(execResult) {
        return [
          parseInt(execResult[1], 16),
          parseInt(execResult[2], 16),
          parseInt(execResult[3], 16)
        ];
      }
    }, {

      // this regex ignores A-F because it's compared against an already lowercased string
      re: /#([a-f0-9])([a-f0-9])([a-f0-9])/,
      parse: function(execResult) {
        return [
          parseInt(execResult[1] + execResult[1], 16),
          parseInt(execResult[2] + execResult[2], 16),
          parseInt(execResult[3] + execResult[3], 16)
        ];
      }
    }, {
      re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,
      space: "hsla",
      parse: function(execResult) {
        return [
          execResult[1],
          execResult[2] / 100,
          execResult[3] / 100,
          execResult[4]
        ];
      }
    }],

    // jQuery.Color( )
    color = jQuery.Color = function(color, green, blue, alpha) {
      return new jQuery.Color.fn.parse(color, green, blue, alpha);
    },
    spaces = {
      rgba: {
        props: {
          red: {
            idx: 0,
            type: "byte"
          },
          green: {
            idx: 1,
            type: "byte"
          },
          blue: {
            idx: 2,
            type: "byte"
          }
        }
      },

      hsla: {
        props: {
          hue: {
            idx: 0,
            type: "degrees"
          },
          saturation: {
            idx: 1,
            type: "percent"
          },
          lightness: {
            idx: 2,
            type: "percent"
          }
        }
      }
    },
    propTypes = {
      "byte": {
        floor: true,
        max: 255
      },
      "percent": {
        max: 1
      },
      "degrees": {
        mod: 360,
        floor: true
      }
    },

    // colors = jQuery.Color.names
    colors,

    // local aliases of functions called often
    each = jQuery.each;

  // define cache name and alpha properties
  // for rgba and hsla spaces
  each(spaces, function(spaceName, space) {
    space.cache = "_" + spaceName;
    space.props.alpha = {
      idx: 3,
      type: "percent",
      def: 1
    };
  });

  function clamp(value, prop, allowEmpty) {
    var type = propTypes[prop.type] || {};

    if (value == null) {
      return (allowEmpty || !prop.def) ? null : prop.def;
    }

    // ~~ is an short way of doing floor for positive numbers
    value = type.floor ? ~~value : parseFloat(value);

    if (type.mod) {

      // we add mod before modding to make sure that negatives values
      // get converted properly: -10 -> 350
      return (value + type.mod) % type.mod;
    }

    // for now all property types without mod have min and max
    return Math.min(type.max, Math.max(0, value));
  }

  function stringParse(string) {
    var inst = color(),
      rgba = inst._rgba = [];

    string = string.toLowerCase();

    each(stringParsers, function(i, parser) {
      var parsed,
        match = parser.re.exec(string),
        values = match && parser.parse(match),
        spaceName = parser.space || "rgba";

      if (values) {
        parsed = inst[spaceName](values);

        // if this was an rgba parse the assignment might happen twice
        // oh well....
        inst[spaces[spaceName].cache] = parsed[spaces[spaceName].cache];
        rgba = inst._rgba = parsed._rgba;

        // exit each(stringParsers) here because we matched
        return false;
      }
    });

    // Found a stringParser that handled it
    if (rgba.length) {

      // if this came from a parsed string, force "transparent" when alpha is 0
      // chrome, (and maybe others) return "transparent" as rgba(0,0,0,0)
      if (rgba.join() === "0,0,0,0") {
        jQuery.extend(rgba, colors.transparent);
      }
      return inst;
    }

    // named colors
    return colors[string];
  }

  color.fn = jQuery.extend(color.prototype, {
    parse: function(red, green, blue, alpha) {
      if (red === undefined) {
        this._rgba = [null, null, null, null];
        return this;
      }
      if (red.jquery || red.nodeType) {
        red = jQuery(red).css(green);
        green = undefined;
      }

      var inst = this,
        type = jQuery.type(red),
        rgba = this._rgba = [];

      // more than 1 argument specified - assume (red, green, blue, alpha)
      if (green !== undefined) {
        red = [red, green, blue, alpha];
        type = "array";
      }

      if (type === "string") {
        return this.parse(stringParse(red) || colors._default);
      }

      if (type === "array") {
        each(spaces.rgba.props, function(key, prop) {
          rgba[prop.idx] = clamp(red[prop.idx], prop);
        });
        return this;
      }

      if (type === "object") {
        if (red instanceof color) {
          each(spaces, function(spaceName, space) {
            if (red[space.cache]) {
              inst[space.cache] = red[space.cache].slice();
            }
          });
        } else {
          each(spaces, function(spaceName, space) {
            var cache = space.cache;
            each(space.props, function(key, prop) {

              // if the cache doesn't exist, and we know how to convert
              if (!inst[cache] && space.to) {

                // if the value was null, we don't need to copy it
                // if the key was alpha, we don't need to copy it either
                if (key === "alpha" || red[key] == null) {
                  return;
                }
                inst[cache] = space.to(inst._rgba);
              }

              // this is the only case where we allow nulls for ALL properties.
              // call clamp with alwaysAllowEmpty
              inst[cache][prop.idx] = clamp(red[key], prop, true);
            });

            // everything defined but alpha?
            if (inst[cache] && jQuery.inArray(null, inst[cache].slice(0, 3)) < 0) {

              // use the default of 1
              inst[cache][3] = 1;
              if (space.from) {
                inst._rgba = space.from(inst[cache]);
              }
            }
          });
        }
        return this;
      }
    },
    is: function(compare) {
      var is = color(compare),
        same = true,
        inst = this;

      each(spaces, function(_, space) {
        var localCache,
          isCache = is[space.cache];
        if (isCache) {
          localCache = inst[space.cache] || space.to && space.to(inst._rgba) || [];
          each(space.props, function(_, prop) {
            if (isCache[prop.idx] != null) {
              same = (isCache[prop.idx] === localCache[prop.idx]);
              return same;
            }
          });
        }
        return same;
      });
      return same;
    },
    _space: function() {
      var used = [],
        inst = this;
      each(spaces, function(spaceName, space) {
        if (inst[space.cache]) {
          used.push(spaceName);
        }
      });
      return used.pop();
    },
    transition: function(other, distance) {
      var end = color(other),
        spaceName = end._space(),
        space = spaces[spaceName],
        startColor = this.alpha() === 0 ? color("transparent") : this,
        start = startColor[space.cache] || space.to(startColor._rgba),
        result = start.slice();

      end = end[space.cache];
      each(space.props, function(key, prop) {
        var index = prop.idx,
          startValue = start[index],
          endValue = end[index],
          type = propTypes[prop.type] || {};

        // if null, don't override start value
        if (endValue === null) {
          return;
        }

        // if null - use end
        if (startValue === null) {
          result[index] = endValue;
        } else {
          if (type.mod) {
            if (endValue - startValue > type.mod / 2) {
              startValue += type.mod;
            } else if (startValue - endValue > type.mod / 2) {
              startValue -= type.mod;
            }
          }
          result[index] = clamp((endValue - startValue) * distance + startValue, prop);
        }
      });
      return this[spaceName](result);
    },
    blend: function(opaque) {

      // if we are already opaque - return ourself
      if (this._rgba[3] === 1) {
        return this;
      }

      var rgb = this._rgba.slice(),
        a = rgb.pop(),
        blend = color(opaque)._rgba;

      return color(jQuery.map(rgb, function(v, i) {
        return (1 - a) * blend[i] + a * v;
      }));
    },
    toRgbaString: function() {
      var prefix = "rgba(",
        rgba = jQuery.map(this._rgba, function(v, i) {
          if (v != null) {
            return v;
          }
          return i > 2 ? 1 : 0;
        });

      if (rgba[3] === 1) {
        rgba.pop();
        prefix = "rgb(";
      }

      return prefix + rgba.join() + ")";
    },
    toHslaString: function() {
      var prefix = "hsla(",
        hsla = jQuery.map(this.hsla(), function(v, i) {
          if (v == null) {
            v = i > 2 ? 1 : 0;
          }

          // catch 1 and 2
          if (i && i < 3) {
            v = Math.round(v * 100) + "%";
          }
          return v;
        });

      if (hsla[3] === 1) {
        hsla.pop();
        prefix = "hsl(";
      }
      return prefix + hsla.join() + ")";
    },
    toHexString: function(includeAlpha) {
      var rgba = this._rgba.slice(),
        alpha = rgba.pop();

      if (includeAlpha) {
        rgba.push(~~(alpha * 255));
      }

      return "#" + jQuery.map(rgba, function(v) {

        // default to 0 when nulls exist
        return ("0" + (v || 0).toString(16)).substr(-2);
      }).join("");
    },
    toString: function() {
      return this._rgba[3] === 0 ? "transparent" : this.toRgbaString();
    }
  });
  color.fn.parse.prototype = color.fn;

  // hsla conversions adapted from:
  // https://code.google.com/p/maashaack/source/browse/packages/graphics/trunk/src/graphics/colors/HUE2RGB.as?r=5021

  function hue2rgb(p, q, h) {
    h = (h + 1) % 1;
    if (h * 6 < 1) {
      return p + (q - p) * h * 6;
    }
    if (h * 2 < 1) {
      return q;
    }
    if (h * 3 < 2) {
      return p + (q - p) * ((2 / 3) - h) * 6;
    }
    return p;
  }

  spaces.hsla.to = function(rgba) {
    if (rgba[0] == null || rgba[1] == null || rgba[2] == null) {
      return [null, null, null, rgba[3]];
    }
    var r = rgba[0] / 255,
      g = rgba[1] / 255,
      b = rgba[2] / 255,
      a = rgba[3],
      max = Math.max(r, g, b),
      min = Math.min(r, g, b),
      diff = max - min,
      add = max + min,
      l = add * 0.5,
      h, s;

    if (min === max) {
      h = 0;
    } else if (r === max) {
      h = (60 * (g - b) / diff) + 360;
    } else if (g === max) {
      h = (60 * (b - r) / diff) + 120;
    } else {
      h = (60 * (r - g) / diff) + 240;
    }

    // chroma (diff) == 0 means greyscale which, by definition, saturation = 0%
    // otherwise, saturation is based on the ratio of chroma (diff) to lightness (add)
    if (diff === 0) {
      s = 0;
    } else if (l <= 0.5) {
      s = diff / add;
    } else {
      s = diff / (2 - add);
    }
    return [Math.round(h) % 360, s, l, a == null ? 1 : a];
  };

  spaces.hsla.from = function(hsla) {
    if (hsla[0] == null || hsla[1] == null || hsla[2] == null) {
      return [null, null, null, hsla[3]];
    }
    var h = hsla[0] / 360,
      s = hsla[1],
      l = hsla[2],
      a = hsla[3],
      q = l <= 0.5 ? l * (1 + s) : l + s - l * s,
      p = 2 * l - q;

    return [
      Math.round(hue2rgb(p, q, h + (1 / 3)) * 255),
      Math.round(hue2rgb(p, q, h) * 255),
      Math.round(hue2rgb(p, q, h - (1 / 3)) * 255),
      a
    ];
  };


  each(spaces, function(spaceName, space) {
    var props = space.props,
      cache = space.cache,
      to = space.to,
      from = space.from;

    // makes rgba() and hsla()
    color.fn[spaceName] = function(value) {

      // generate a cache for this space if it doesn't exist
      if (to && !this[cache]) {
        this[cache] = to(this._rgba);
      }
      if (value === undefined) {
        return this[cache].slice();
      }

      var ret,
        type = jQuery.type(value),
        arr = (type === "array" || type === "object") ? value : arguments,
        local = this[cache].slice();

      each(props, function(key, prop) {
        var val = arr[type === "object" ? key : prop.idx];
        if (val == null) {
          val = local[prop.idx];
        }
        local[prop.idx] = clamp(val, prop);
      });

      if (from) {
        ret = color(from(local));
        ret[cache] = local;
        return ret;
      } else {
        return color(local);
      }
    };

    // makes red() green() blue() alpha() hue() saturation() lightness()
    each(props, function(key, prop) {

      // alpha is included in more than one space
      if (color.fn[key]) {
        return;
      }
      color.fn[key] = function(value) {
        var local, cur, match, fn,
          vtype = jQuery.type(value);

        if (key === "alpha") {
          fn = this._hsla ? "hsla" : "rgba";
        } else {
          fn = spaceName;
        }
        local = this[fn]();
        cur = local[prop.idx];

        if (vtype === "undefined") {
          return cur;
        }

        if (vtype === "function") {
          value = value.call(this, cur);
          vtype = jQuery.type(value);
        }
        if (value == null && prop.empty) {
          return this;
        }
        if (vtype === "string") {
          match = rplusequals.exec(value);
          if (match) {
            value = cur + parseFloat(match[2]) * (match[1] === "+" ? 1 : -1);
          }
        }
        local[prop.idx] = value;
        return this[fn](local);
      };
    });
  });

  // add cssHook and .fx.step function for each named hook.
  // accept a space separated string of properties
  color.hook = function(hook) {
    var hooks = hook.split(" ");
    each(hooks, function(i, hook) {
      jQuery.cssHooks[hook] = {
        set: function(elem, value) {
          var parsed;

          if (value !== "transparent" && (jQuery.type(value) !== "string" || (parsed = stringParse(value)))) {
            value = color(parsed || value);
            value = value.toRgbaString();
          }
          elem.style[hook] = value;
        }
      };
      jQuery.fx.step[hook] = function(fx) {
        if (!fx.colorInit) {
          fx.start = color(fx.elem, hook);
          fx.end = color(fx.end);
          fx.colorInit = true;
        }
        jQuery.cssHooks[hook].set(fx.elem, fx.start.transition(fx.end, fx.pos));
      };
    });

  };

  color.hook(stepHooks);

  jQuery.cssHooks.borderColor = {
    expand: function(value) {
      var expanded = {};

      each(["Top", "Right", "Bottom", "Left"], function(i, part) {
        expanded["border" + part + "Color"] = value;
      });
      return expanded;
    }
  };

  // Basic color names only.
  // Usage of any of the other color names requires adding yourself or including
  // jquery.color.svg-names.js.
  colors = jQuery.Color.names = {

    // 4.1. Basic color keywords
    aqua: "#00ffff",
    black: "#000000",
    blue: "#0000ff",
    fuchsia: "#ff00ff",
    gray: "#808080",
    green: "#008000",
    lime: "#00ff00",
    maroon: "#800000",
    navy: "#000080",
    olive: "#808000",
    purple: "#800080",
    red: "#ff0000",
    silver: "#c0c0c0",
    teal: "#008080",
    white: "#ffffff",
    yellow: "#ffff00",

    // 4.2.3. "transparent" color keyword
    transparent: [null, null, null, 0],

    _default: "#ffffff"
  };

});
